home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / ucomm < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  40.0 KB  |  1,097 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) ucomm.gawk 1.1 96/05/30
  3. # 94/12/27 john h. dubois iii (john@armory.com)
  4. # 96/03/27 Let 0 or 1 args be given.
  5. # 96/04/01 Added &taAdLiIpP options.
  6. # ucomm: a generalized 'comm' that does not require the files to be sorted.
  7.  
  8. ### Start of setup blocks
  9. BEGIN {
  10.     Name = "ucomm"
  11.     Usage = "Usage: " Name " [-aAdhiIlLnox] [-<field-num>] [-t<field-sep>]\n"\
  12.             "             [-p<pattern>|-P<pattern>] selector[,selector...] "\
  13.         "[file1 file2 ...]"
  14.     ARGC = Opts(Name,Usage,"aAdhiIlLnop:P:x&>t:",0)
  15.     if ((Err = ExclusiveOptions("p,P;l,n;a,A,d;L,d",Options)) != "") {
  16.     printf "Error: %s\n",Err > "/dev/stderr"
  17.     Err = 1
  18.     exit(1)
  19.     }
  20.     if ("h" in Options) {
  21.     printf \
  22. "%s: show which files lines occur in.\n"\
  23. "     %s takes a list of common-line selectors and a list of filenames and\n"\
  24. "shows lines from the files that are selected.  Selectors have the form\n"\
  25. "nn...  where each n is a digit or letter specifying a file number.  1\n"\
  26. "refers to the first file given on the command line, 2 to the second, etc.\n"\
  27. "The letter 'a' refers to the 10th, and 'z' to the 35th.  Each selector\n"\
  28. "causes lines that occur in all of the files whose numbers are in the\n"\
  29. "selector, and which occur only in those files, to be printed.  If the line\n"\
  30. "occurs in any files whose number is not in the selector, the selector will\n"\
  31. "not cause it to be printed, though another selector might.  File numbers\n"\
  32. "higher than the number of filenames given are ignored with a warning.\n"\
  33. "     A range of file numbers may be given as part of a selector.  For\n"\
  34. "example, the selector 13-c would specify file 1 and files 3 through c\n"\
  35. "inclusive.  $ refers to the last file on the command line.  A file number\n"\
  36. "of '%%' is short for 1,2,...9,a,...z; that is, it causes any line which\n"\
  37. "occurs in only one file to be printed.  If any other file numbers are\n"\
  38. "included with a '%%' selector, then any lines that occurs in all of the\n"\
  39. "specifically named files, plus one other, are selected.  '~' complements a\n"\
  40. "selector with respect to the set of all file numbers.  It can be given\n"\
  41. "anywhere in a selector.  If given by itself, it is equivalent to 1-$,\n"\
  42. "which causes any line which occurs in all files to be printed.  If any\n"\
  43. "other file numbers are included with a '~' selector, then any lines that\n"\
  44. "occur in all files except those specifically named are selected.  '*' will\n"\
  45. "cause all lines to be selected.  If a selector begins with '!' or '^', it\n"\
  46. "will suppress printing of lines that it selects even if other selectors\n"\
  47. "would cause them to be printed.  An example of its use would be the\n"\
  48. "selector '*,!~' which would print all lines except those that occur in all\n"\
  49. "of the files.  Note that various of the characters $~*!^ are special to\n"\
  50. "the shell and need to be quoted or escaped.\n"\
  51. "     If more than one selector is given, each output line is prefixed by\n"\
  52. "the selector that caused it to be printed followed by a colon and a space.\n"\
  53. "For example: \n"\
  54. "13ac: this is a line that occurs in files 1, 3, a, and c\n"\
  55. "The selector part of the line prefix is effectively be a list of the files\n"\
  56. "the line was found in.  No line is printed more than once, even if it\n"\
  57. "occurs more than once in a file.  The order of the output lines is not the\n"\
  58. "same as the order in which lines are read.\n"\
  59. "The file name /dev/stdin can be used for the standard input, and /dev/fd/n\n"\
  60. "can be used for file descriptor n.  If no filenames are given or only one\n"\
  61. "filename is given, no selector may be given; a single instance of each\n"\
  62. "line that occurs in the file or in the standard input respectively is\n"\
  63. "printed.\n"\
  64. "%s\n"\
  65. "Other options:\n"\
  66. "-[pP]<pattern>: In all input files, ignore (discard) all lines that do not\n"\
  67. "    (-p) or do (-P) match <pattern>, which is in the style of egrep(C).\n"\
  68. "-i: Ignore case when determining uniqueness of lines.\n"\
  69. "-I: Ignore case in pattern matches used for -[pP].\n"\
  70. "-h: Print this help.\n"\
  71. "-l: Make the selected-line prefix consist only of file numbers.  If a\n"\
  72. "    selector includes a range or one of the special characters [$%%~!^*],\n"\
  73. "    they are converted into file numbers.\n"\
  74. "-n: Do not prefix output lines with the selector.\n"\
  75. "-o: Retain line order.  In the output, lines are printed in the same order\n"\
  76. "    as they were first encountered in the input.  This option is expensive\n"\
  77. "    in terms of memory use.\n"\
  78. "-x: Print debugging info, including expanded versions of given selectors.\n"\
  79. "-<field-num>: Check only the given field number of each line when\n"\
  80. "    comparing them to others for uniqueness.  Fields are separated by\n"\
  81. "    whitespace (tabs and blanks).  If a line begins with whitespace, the\n"\
  82. "    first field is the string that occurs between the initial whitespace\n"\
  83. "    and the next instance of whitespace (or the end of the line).  In the\n"\
  84. "    output, by default the first instance of the line is printed.\n"\
  85. "The rest of the options are used only when -<field-num> is given:\n"\
  86. "-a: Print all unique lines selected by -<field-num>.\n"\
  87. "-A: Like -a, except that a blank line is printed between each set of lines\n"\
  88. "    that have the same field.\n"\
  89. "-L: Used with -a or -A; prefix each line with the numbers of the files the\n"\
  90. "    line was found in.\n"\
  91. "-d: Print only the field that is being checked, not the whole line.\n"\
  92. "-t<field-sep>: Set the field separator to <field-sep>.  <field-sep> is a\n"\
  93. "    pattern in the style of egrep(C).  If this option is given and a line\n"\
  94. "    begins with a string that matches the pattern, the first field is\n"\
  95. "    empty and the second field occurs after the initial matched characters.\n"\
  96. "    Thus, -t'[ \\t]+' is different from the default field separator in the\n"\
  97. "    way that fields on a line that begins with whitespace are numbered.\n",
  98. Name,Name,Usage
  99.     exit 0
  100.     }
  101.     if (ARGC == 3) {
  102.     print Name ": Wrong number of arguments.  Use -h for help.\n"\
  103.     Usage > "/dev/stderr"
  104.     exit 1
  105.     }
  106.     if (ARGC > 3)
  107.     NumFiles = ARGC - 2        # Skip program name & selector
  108.     else {
  109.     # If 1 arg (ARGC = 2) is given, it is a filename.
  110.     if (ARGC == 1)    # If 0 args, make input be stdin
  111.         ARGV[2] = "/dev/stdin"
  112.     else
  113.         ARGV[2] = ARGV[1]    # move filename to correct place
  114.     # If 0 or 1 args, set selector to 1 and # of files to 1
  115.     NumFiles = 1
  116.     ARGV[1] = "1"
  117.     ARGC = 3    # for gawk
  118.     }
  119.     if (NumFiles > 35) {    # 1-9a-z = 35
  120.     print "Cannot process more than 35 files." > "/dev/stderr"
  121.     exit 1
  122.     }
  123.     # Char n of this string is the fileno char for file n
  124.     AllNums = substr("123456789abcdefghijklmnopqrstuvwxyz",1,NumFiles)
  125.     if ((NumSel = ProcSelectors(ARGV[1],Selectors,UnSelectors)) == -1)
  126.     exit 1
  127.     if (NumSel == 0) {
  128.     print "No selectors." > "/dev/stderr"
  129.     Err = "0"
  130.     }
  131.     if (Err != "")
  132.     exit Err
  133.     ARGV[1] = "/dev/null"    # skip selector arg
  134.     Ordered = "o" in Options
  135.     if (GotPat = ("p" in Options || "P" in Options)) {
  136.     if ("p" in Options)
  137.         Pat = Options["p"]
  138.     if ("P" in Options)
  139.         NotPat = Options["P"]
  140.     }
  141.     if ("&" in Options) {
  142.     FieldNum = Options["&"]
  143.     WholeLine = !("d" in Options)    # So whole line will be printed
  144.     # If this is on, WholeLine is too
  145.     AllLines = "a" in Options || "A" in Options
  146.     SeparateLines = "A" in Options
  147.     PrefixEachLine = "L" in Options
  148.     }
  149.     else
  150.     FieldNum = 0    # Compare entire line
  151.     if ("t" in Options)
  152.     FS = Options["t"]
  153.     if (Debug = ("x" in Options)) {
  154.     print "Field number: " FieldNum
  155.     print "Field sep: " FS
  156.     print "Selectors:" > "/dev/stderr"
  157.     for (i in Selectors)
  158.         print i > "/dev/stderr"
  159.     print "Exclusion selectors:" > "/dev/stderr"
  160.     for (i in UnSelectors)
  161.         print i > "/dev/stderr"
  162.     }
  163.     IGNORECASE = "I" in Options
  164.     if ("i" in Options) {
  165.     NoCase = 1
  166.     # If not working with fields and case is being ignored, turn on 
  167.     # Ordered option because it will cause case to be saved as a side
  168.     # effect.
  169.     if (FieldNum == 0)
  170.         Ordered = 1
  171.     }
  172. }
  173.  
  174. # Returns the number of selectors, or -1 if a fatal error is encountered
  175. function ProcSelectors(SelString,Selectors,UnSelectors,
  176. i,NSel,Sels,Sel,c,Not,Bad,Err,NumSel) {
  177.     NSel = split(SelString,Sels,",")
  178.     for (i = 1; i <= NSel; i++) {
  179.     Sel = tolower(Sels[i])        # Let A-Z also work
  180.     if (Not = ((c = substr(Sel,1,1)) == "!" || c == "^"))
  181.         Sel = substr(Sel,2)
  182.     if (Sel == "")    # Empty selector; ignore it
  183.         continue
  184.     if (Sel !~ /^[-1-9a-z$%~*]+$/) {
  185.         Bad = Sel
  186.         gsub(/[-1-9a-z$%~*]+/,"",Bad)
  187.         printf "Invalid character(s) in selector \"%s\": %s\n",
  188.         Sel,Bad > "/dev/stderr"
  189.         Err = 1
  190.         continue
  191.     }
  192.     if (!Not)
  193.         NumSel++
  194.     if (Not)    # Can't use ternary operator to select an array
  195.         Err = ExpandSel(Sel,UnSelectors) || Err
  196.     else
  197.         Err = ExpandSel(Sel,Selectors) || Err
  198.     }
  199.     if ("*" in UnSelect) {
  200.     print "* is not valid in an exclusion selector." > "/dev/stderr"
  201.     Err = 1
  202.     }
  203.     return Err ? -1 : NumSel
  204. }
  205.  
  206. # Expands selector Sel into a simple selector and makes it an index of SelSet,
  207. # with the value being the original selector.
  208. # All selectors put in SelSet will consist of of a sorted sequence of valid
  209. # file number characters, except that the special selector '*' is stored to
  210. # indicate that all lines should be selected.
  211. function ExpandSel(Sel,SelSet,
  212. i,c,len,Set,Not,One,LastC,RangeStart,RangeEnd,Err,ExpSel,RangeC) {
  213.     Err = 0
  214.     len = length(Sel)
  215.     for (i = 1; i <= len; i++) {
  216.     c = substr(Sel,i,1)
  217.     if (c == "$")    # Replace $ with number of last file
  218.         c = Num2FileChar(NumFiles)
  219.     if (c ~ /[1-9a-z]/) {
  220.         if (!FileChar2Num(c))
  221.         printf \
  222. "Warning: selector char '%s': Not that many files given on command line.\n",
  223.         c > "/dev/stderr"
  224.         # If we are expecting the 2nd char of a range
  225.         if (RangeStart > 0) {    
  226.         if (!(RangeEnd = FileChar2Num(c))) {
  227.             print "Invalid character in selector range: %s\n",
  228.             c > "/dev/stderr"
  229.             Err = 1
  230.         }
  231.         else if (RangeEnd < RangeStart) {
  232.             printf "Bad range: %s-%s\n",
  233.             RangeStart,RangeEnd > "/dev/stderr"
  234.             Err = 1
  235.         }
  236.         else for (RangeC = RangeStart+1; RangeC <= RangeEnd; RangeC++)
  237.             Set[Num2FileChar(RangeC)]
  238.         RangeStart = 0
  239.         }
  240.         else
  241.         Set[c]
  242.     }
  243.     # Record that % or ~ was found, for processing at the end
  244.     else if (c == "%")
  245.         One = 1
  246.     else if (c == "~")
  247.         Not = 1
  248.     else if (c == "*")
  249.         SelSet[c] = Sel
  250.     else if (c == "-") {    # The last char was the start of a range
  251.         if (!(RangeStart = FileChar2Num(LastC))) {
  252.         print "Invalid character in selector range: %s\n",
  253.         LastC > "/dev/stderr"
  254.         Err = 1
  255.         }
  256.     }
  257.     LastC = c
  258.     }
  259.     if (Not)
  260.     ComplementFiles(Set)
  261.     if (One)
  262.     for (i = 1; i <= NumFiles; i++) {
  263.         c = Num2FileChar(i)
  264.         if (!(c in Set)) {
  265.         Set[c]
  266.         SelSet[Set2Sel(Set)] = Sel
  267.         delete Set[c]
  268.         }
  269.     }
  270.     else {
  271.     ExpSel = Set2Sel(Set)
  272.     if (ExpSel != "")
  273.         SelSet[ExpSel] = Sel
  274.     }
  275.     return Err
  276. }
  277.  
  278. function Set2Sel(Set,  i,c,ExpSel) {
  279.     for (i = 1; i <= NumFiles; i++)
  280.     if ((c = Num2FileChar(i)) in Set)
  281.         ExpSel = ExpSel c
  282.     return ExpSel
  283. }
  284.  
  285. function ComplementFiles(Set,  i,c) {
  286.     for (i = 1; i <= NumFiles; i++) {
  287.     c = Num2FileChar(i)
  288.     if (c in Set)
  289.         delete Set[c]
  290.     else
  291.         Set[c]
  292.     }
  293. }
  294.  
  295. # Convert numbers 1-35 to chars 1-9a-z
  296. function Num2FileChar(Num) {
  297.     return substr(AllNums,Num,1)
  298. }
  299.  
  300. function FileChar2Num(C) {
  301.     return index(AllNums,C)
  302. }
  303.  
  304. ### Start of file processing blocks
  305.  
  306. # If FileNum is not already part of Lines[Index], add it.
  307. function AddFileNum(Lines,Index,  FFiles) {
  308.     FFiles = Lines[Index]    # Get its found-files record
  309.     if (substr(FFiles,length(FFiles),1) != FileNum)
  310.         Lines[Index] = Lines[Index] FileNum
  311. }
  312.  
  313. ARGIND != OldInd {
  314.     FileNum = Num2FileChar(ARGIND-1)    # First file is arg #2
  315.     OldInd = ARGIND
  316. }
  317.  
  318. GotPat {
  319.     if ((Pat != "" && $0 !~ Pat) || (NotPat != "" && $0 ~ NotPat))
  320.     next
  321. }
  322.  
  323. # Lines are stored as indexes in Lines[].
  324. # The value associated with the index is the set of files that the line
  325. # was found in, first through last.
  326. # If -o is given, the line is also stored as a value in Order[],
  327. # with the index being an integer giving the order in which the line was found.
  328. {
  329.     Index = NoCase ? tolower($FieldNum) : $FieldNum
  330.     if (Index in Lines) {        # If the line/field was already seen
  331.     AddFileNum(Lines,Index)
  332.     if (AllLines) {
  333.         if ($0 in TrackWholeLines) {
  334.         if (PrefixEachLine)
  335.             AddFileNum(TrackWholeLines,$0)
  336.         }
  337.         else {
  338.         Field2Line[Index] = Field2Line[Index] "\n" $0
  339.         TrackWholeLines[$0] = FileNum
  340.         }
  341.     }
  342.     }
  343.     else {
  344.     Lines[Index] = FileNum
  345.     if (Ordered)
  346.         Order[++LineCt] = $FieldNum
  347.     if (Debug)
  348.         printf "New field #%d%s: %s\n",FieldNum,
  349.         Ordered ? (" (now " LineCt ")") : "",Index
  350.     if (WholeLine) {
  351.         Field2Line[Index] = $0
  352.         if (AllLines)
  353.         TrackWholeLines[$0] = FileNum
  354.     }
  355.     }
  356. }
  357.  
  358. ### Start of report generating blocks
  359.  
  360. END {
  361.     if (Err != "")
  362.     exit Err
  363.     PrintExpSel = "l" in Options
  364.     if (!(NoSel = "n" in Options)) {        # Do not print selectors?
  365.     # If -n wasn't given, print selectors only if at least 2 given.
  366.     # If printing expanded selectors, can't rely on the value NumSel
  367.     # already has, since it counts e.g. % as 1 selector.
  368.     if (PrintExpSel) {
  369.         NumSel = 0
  370.         for (i in Selectors)
  371.         if (++NumSel == 2)
  372.             break
  373.     }
  374.     NoSel = NumSel < 2
  375.     }
  376.     AllSelect = "*" in Selectors
  377.     if (Ordered)
  378.     for (i = 1; i <= LineCt; i++)
  379.         CheckLine(Order[i])
  380.     else
  381.     for (Line in Lines)
  382.         CheckLine(Line)
  383. }
  384.  
  385. # If NoCase is true, the indexes in Lines[], TrackWholeLines[],
  386. # and Field2Line[] are in lowercase.
  387. function CheckLine(Line,  FFiles,nLine,iLines,i,l,Index) {
  388.     Index = NoCase ? tolower(Line) : Line
  389.     FFiles = Lines[Index]    # Get this line's found-files record
  390.     if ((AllSelect || FFiles in Selectors) && !(FFiles in UnSelectors)) {
  391.     if (SeparateLines)
  392.         if (NotFirst)
  393.         print ""
  394.         else
  395.         NotFirst = 1
  396.     if (!NoSel)
  397.         printf "%s: ",
  398.         AllSelect ? "*" : (PrintExpSel ? FFiles : Selectors[FFiles])
  399.     if (PrefixEachLine) {
  400.         nLine = split(Field2Line[Index],iLines,"\n")
  401.         for (i = 1; i <= nLine; i++) {
  402.         l = iLines[i]
  403.         printf "%s) %s\n",TrackWholeLines[l],l
  404.         }
  405.     }
  406.     else
  407.         print WholeLine ? Field2Line[Index] : Line
  408.     }
  409. }
  410.  
  411. ### Start of library functions
  412.  
  413. ### Start of ProcArgs library
  414. # @(#) ProcArgs 1.11 96/12/08
  415. # 92/02/29 john h. dubois iii (john@armory.com)
  416. # 93/07/18 Added "#" arg type
  417. # 93/09/26 Do not count -h against MinArgs
  418. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  419. #          Removed meaning of "+" or "-" by itself.
  420. # 94/03/08 Added & option and *()< option types.
  421. # 94/04/02 Added NoRCopt to Opts()
  422. # 94/06/11 Mark numeric variables as such.
  423. # 94/07/08 Opts(): Do not require any args if h option is given.
  424. # 95/01/22 Record options given more than once.  Record option num in argv.
  425. # 95/06/08 Added ExclusiveOptions().
  426. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  427. #          Expand $VARNAME at the start of its filenames.
  428. #          Let varname=0 and -option- turn off an option.
  429. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  430. #          of the vars should be searched for in the environment.
  431. #          Check for duplicate rcfiles.
  432. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  433. #          now return various negatives values on error, not just -1, and
  434. #          Opts() may set Err to various positive values, not just 1.
  435. #          Added AllowUnrecOpt.
  436. # 96/05/23 Check type given for & option
  437. # 96/06/15 Re-port to awk
  438. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  439. #          used by other functions.
  440. # 96/10/15 Added OptChars
  441. # 96/11/01 Added exOpts arg to Opts()
  442. # 96/11/16 Added ; type
  443. # 96/12/08 Added Opt2Set() & Opt2Sets()
  444. # 96/12/27 Added CmdLineOpt()
  445.  
  446. # optlist is a string which contains all of the possible command line options.
  447. # A character followed by certain characters indicates that the option takes
  448. # an argument, with type as follows:
  449. # :    String argument
  450. # ;    Non-empty string argument
  451. # *    Floating point argument
  452. # (    Non-negative floating point argument
  453. # )    Positive floating point argument
  454. # #    Integer argument
  455. # <    Non-negative integer argument
  456. # >    Positive integer argument
  457. # The only difference the type of argument makes is in the runtime argument
  458. # error checking that is done.
  459.  
  460. # The & option is a special case used to get numeric options without the
  461. # user having to give an option character.  It is shorthand for [-+.0-9].
  462. # If & is included in optlist and an option string that begins with one of
  463. # these characters is seen, the value given to "&" will include the first
  464. # char of the option.  & must be followed by a type character other than ":"
  465. # or ";".
  466. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  467.  
  468. # Strings in argv[] which begin with "-" or "+" are taken to be
  469. # strings of options, except that a string which consists solely of "-"
  470. # or "+" is taken to be a non-option string; like other non-option strings,
  471. # it stops the scanning of argv and is left in argv[].
  472. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  473. # If an option takes an argument, the argument may either immediately
  474. # follow it or be given separately.
  475. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  476. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  477. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  478. # this feature had a flaw that caused problems in some cases.  See the OptChars
  479. # parameter to explicitly set the option-specifier characters.
  480.  
  481. # If an option that does not take an argument is given,
  482. # an index with its name is created in Options and its value is set to the
  483. # number of times it occurs in argv[].
  484.  
  485. # If an option that does take an argument is given, an index with its name is
  486. # created in Options and its value is set to the value of the argument given
  487. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  488. # If an option that takes an argument is given more than once,
  489. # Options[option-name,"count"] is incremented, and the value is assigned to
  490. # the index (option-name,instance) where instance is 2 for the second occurance
  491. # of the option, etc.
  492. # In other words, the first time an option with a value is encountered, the
  493. # value is assigned to an index consisting only of its name; for any further
  494. # occurances of the option, the value index has an extra (count) dimension.
  495.  
  496. # The sequence number for each option found in argv[] is stored in
  497. # Options[option-name,"num",instance], where instance is 1 for the first
  498. # occurance of the option, etc.  The sequence number starts at 1 and is
  499. # incremented for each option, both those that have a value and those that
  500. # do not.  Options set from a config file have a value of 0 assigned to this.
  501.  
  502. # Options and their arguments are deleted from argv.
  503. # Note that this means that there may be gaps left in the indices of argv[].
  504. # If compress is nonzero, argv[] is packed by moving its elements so that
  505. # they have contiguous integer indices starting with 0.
  506. # Option processing will stop with the first unrecognized option, just as
  507. # though -- was given except that unlike -- the unrecognized option will not be
  508. # removed from ARGV[].  Normally, an error value is returned in this case.
  509. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  510. # be found, so the number of remaining arguments is returned instead.
  511. # If OptChars is not a null string, it is the set of characters that indicate
  512. # that an argument is an option string if the string begins with one of the
  513. # characters.  A string consisting solely of two of the same option-indicator
  514. # characters stops the scanning of argv[].  The default is "-+".
  515. # argv[0] is not examined.
  516. # The number of arguments left in argc is returned.
  517. # If an error occurs, the global string OptErr is set to an error message
  518. # and a negative value is returned.
  519. # Current error values:
  520. # -1: option that required an argument did not get it.
  521. # -2: argument of incorrect type supplied for an option.
  522. # -3: unrecognized (invalid) option.
  523. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  524. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  525. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  526. {
  527. # ArgNum is the index of the argument being processed.
  528. # ArgsLeft is the number of arguments left in argv.
  529. # Arg is the argument being processed.
  530. # ArgLen is the length of the argument being processed.
  531. # ArgInd is the position of the character in Arg being processed.
  532. # Option is the character in Arg being processed.
  533. # Pos is the position in OptList of the option being processed.
  534. # NumOpt is true if a numeric option may be given.
  535.     ArgsLeft = argc
  536.     NumOpt = index(OptList,"&")
  537.     OptionNum = 0
  538.     if (OptChars == "")
  539.     OptChars = "-+"
  540.     while (OptChars != "") {
  541.     c = substr(OptChars,1,1)
  542.     OptChars = substr(OptChars,2)
  543.     OptCharSet[c]
  544.     OptTerm[c c]
  545.     }
  546.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  547.     Arg = argv[ArgNum]
  548.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  549.         break    # Not an option; quit
  550.     if (Arg in OptTerm) {
  551.         delete argv[ArgNum]
  552.         ArgsLeft--
  553.         break
  554.     }
  555.     ArgLen = length(Arg)
  556.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  557.         Option = substr(Arg,ArgInd,1)
  558.         if (NumOpt && Option ~ /[-+.0-9]/) {
  559.         # If this option is a numeric option, make its flag be & and
  560.         # its option string flag position be the position of & in
  561.         # the option string.
  562.         Option = "&"
  563.         Pos = NumOpt
  564.         # Prefix Arg with a char so that ArgInd will point to the
  565.         # first char of the numeric option.
  566.         Arg = "&" Arg
  567.         ArgLen++
  568.         }
  569.         # Find position of flag in option string, to get its type (if any).
  570.         # Disallow & as literal flag.
  571.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  572.         if (AllowUnrecOpt) {
  573.             Escape = 1
  574.             break
  575.         }
  576.         else {
  577.             OptErr = "Invalid option: " specGiven Option
  578.             return -3
  579.         }
  580.         }
  581.  
  582.         # Find what the value of the option will be if it takes one.
  583.         # NeedNextOpt is true if the option specifier is the last char of
  584.         # this arg, which means that if the option requires a value it is
  585.         # the next arg.
  586.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  587.         if (GotValue = ArgNum + 1 < argc)
  588.             Value = argv[ArgNum+1]
  589.         }
  590.         else {    # Value is included with option
  591.         Value = substr(Arg,ArgInd + 1)
  592.         GotValue = 1
  593.         }
  594.  
  595.         if (HadValue = AssignVal(Option,Value,Options,
  596.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  597.         specGiven)) {
  598.         if (HadValue < 0)    # error occured
  599.             return HadValue
  600.         if (HadValue == 2)
  601.             ArgInd++    # Account for the single-char value we used.
  602.         else {
  603.             if (NeedNextOpt) {    # option took next arg as value
  604.             delete argv[++ArgNum]
  605.             ArgsLeft--
  606.             }
  607.             break    # This option has been used up
  608.         }
  609.         }
  610.     }
  611.     if (Escape)
  612.         break
  613.     # Do not delete arg until after processing of it, so that if it is not
  614.     # recognized it can be left in ARGV[].
  615.     delete argv[ArgNum]
  616.     ArgsLeft--
  617.     }
  618.     if (compress != 0) {
  619.     dest = 1
  620.     src = argc - ArgsLeft + 1
  621.     for (count = ArgsLeft - 1; count; count--) {
  622.         ARGV[dest] = ARGV[src]
  623.         dest++
  624.         src++
  625.     }
  626.     }
  627.     return ArgsLeft
  628. }
  629.  
  630. # Assignment to values in Options[] occurs only in this function.
  631. # Option: Option specifier character.
  632. # Value: Value to be assigned to option, if it takes a value.
  633. # Options[]: Options array to return values in.
  634. # ArgType: Argument type specifier character.
  635. # GotValue: Whether any value is available to be assigned to this option.
  636. # Name: Name of option being processed.
  637. # OptionNum: Number of this option (starting with 1) if set in argv[],
  638. #     or 0 if it was given in a config file or in the environment.
  639. # SingleOpt: true if the value (if any) that is available for this option was
  640. #     given as part of the same command line arg as the option.  Used only for
  641. #     options from the command line.
  642. # specGiven is the option specifier character use, if any (e.g. - or +),
  643. # for use in error messages.
  644. # Global variables: OptErr
  645. # Return value: negative value on error, 0 if option did not require an
  646. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  647. # the arg.
  648. # Current error values:
  649. # -1: Option that required an argument did not get it.
  650. # -2: Value of incorrect type supplied for option.
  651. # -3: Bad type given for option &
  652. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  653. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  654.     # If option takes a value...    [
  655.     NumTypes = "*()#<>]"
  656.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  657.     OptErr = "Bad type given for & option"
  658.     return -3
  659.     }
  660.  
  661.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  662.     if (!GotValue) {
  663.         if (Name != "")
  664.         OptErr = "Variable requires a value -- " Name
  665.         else
  666.         OptErr = "option requires an argument -- " Option
  667.         return -1
  668.     }
  669.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  670.         OptErr = Err
  671.         return -2
  672.     }
  673.     # Mark this as a numeric variable; will be propogated to Options[] val.
  674.     if (ArgType != ":" && ArgType != ";")
  675.         Value += 0
  676.     if ((Instance = ++Options[Option,"count"]) > 1)
  677.         Options[Option,Instance] = Value
  678.     else
  679.         Options[Option] = Value
  680.     }
  681.     # If this is an environ or rcfile assignment & it was given a value...
  682.     else if (!OptionNum && Value != "") {
  683.     UsedValue = 1
  684.     # If the value is "0" or "-" and this is the first instance of it,
  685.     # do not set Options[Option]; this allows an assignment in an rcfile to
  686.     # turn off an option (for the simple "Option in Options" test) in such
  687.     # a way that it cannot be turned on in a later file.
  688.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  689.         Instance = 1
  690.     else
  691.         Instance = ++Options[Option]
  692.     # Save the value even though this is a flag
  693.     Options[Option,Instance] = Value
  694.     }
  695.     # If this is a command line flag and has a - following it in the same arg,
  696.     # it is being turned off.
  697.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  698.     UsedValue = 2
  699.     if (Option in Options)
  700.         Instance = ++Options[Option]
  701.     else
  702.         Instance = 1
  703.     Options[Option,Instance]
  704.     }
  705.     # If this is a flag assignment without a value, increment the count for the
  706.     # flag unless it was turned off.  The indicator for a flag being turned off
  707.     # is that the flag index has not been set in Options[] but it has an
  708.     # instance count.
  709.     else if (Option in Options || !((Option,1) in Options))
  710.     # Increment number of times this flag seen; will inc null value to 1
  711.     Instance = ++Options[Option]
  712.     Options[Option,"num",Instance] = OptionNum
  713.     return UsedValue
  714. }
  715.  
  716. # Option is the option letter
  717. # Value is the value being assigned
  718. # Name is the var name of the option, if any
  719. # ArgType is one of:
  720. # :    String argument
  721. # ;    Non-null string argument
  722. # *    Floating point argument
  723. # (    Non-negative floating point argument
  724. # )    Positive floating point argument
  725. # #    Integer argument
  726. # <    Non-negative integer argument
  727. # >    Positive integer argument
  728. # specGiven is the option specifier character use, if any (e.g. - or +),
  729. # for use in error messages.
  730. # Returns null on success, err string on error
  731. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  732.     if (ArgType == ":")
  733.     return ""
  734.     if (ArgType == ";") {
  735.     if (Value == "")
  736.         Err = "must be a non-empty string"
  737.     }
  738.     # A number begins with optional + or -, and is followed by a string of
  739.     # digits or a decimal with digits before it, after it, or both
  740.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  741.     Err = "must be a number"
  742.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  743.     Err = "may not include a fraction"
  744.     else if (ArgType ~ "[()<>]" && Value < 0)
  745.     Err = "may not be negative"
  746.     # (
  747.     else if (ArgType ~ "[)>]" && Value == 0)
  748.     Err = "must be a positive number"
  749.     if (Err != "") {
  750.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  751.     if (Name != "")
  752.         return ErrStr "variable " substr(Name,1,1) " " Err
  753.     else {
  754.         if (Option == "&")
  755.         Option = Value
  756.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  757.     }
  758.     }
  759.     else
  760.     return ""
  761. }
  762.  
  763. # Note: only the above functions are needed by ProcArgs.
  764. # The rest of these functions call ProcArgs() and also do other
  765. # option-processing stuff.
  766.  
  767. # Opts: Process command line arguments.
  768. # Opts processes command line arguments using ProcArgs()
  769. # and checks for errors.  If an error occurs, a message is printed
  770. # and the program is exited.
  771. #
  772. # Input variables:
  773. # Name is the name of the program, for error messages.
  774. # Usage is a usage message, for error messages.
  775. # OptList the option description string, as used by ProcArgs().
  776. # MinArgs is the minimum number of non-option arguments that this
  777. # program should have, non including ARGV[0] and +h.
  778. # If the program does not require any non-option arguments,
  779. # MinArgs should be omitted or given as 0.
  780. # rcFiles, if given, is a colon-seprated list of filenames to read for
  781. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  782. # by the value of the environment variable HOME.  If a filename begins with
  783. # $, the part from the character after the $ up until (but not including)
  784. # the first character not in [a-zA-Z0-9_] will be searched for in the
  785. # environment; if found its value will be substituted, if not the filename will
  786. # be discarded.
  787. # rcfiles are read in the order given.
  788. # Values given in them will not override values given on the command line,
  789. # and values given in later files will not override those set in earlier
  790. # files, because AssignVal() will store each with a different instance index.
  791. # The first instance of each variable, either on the command line or in an
  792. # rcfile, will be stored with no instance index, and this is the value
  793. # normally used by programs that call this function.
  794. # VarNames is a comma-separated list of variable names to map to options,
  795. # in the same order as the options are given in OptList.
  796. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  797. # searched for in the environment.  If set to -1, all values will be searched
  798. # for in the environment.  Values given in the environment will override
  799. # those given in the rcfiles but not those given on the command line.
  800. # NoRCopt, if given, is an additional letter option that if given on the
  801. # command line prevents the rcfiles from being read.
  802. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  803. # ExclusiveOptions() for a description of exOpts.
  804. # Special options:
  805. # If x is made an option and is given, some debugging info is output.
  806. # h is assumed to be the help option.
  807.  
  808. # Global variables:
  809. # The command line arguments are taken from ARGV[].
  810. # The arguments that are option specifiers and values are removed from
  811. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  812. # The number of elements in ARGV[] should be in ARGC.
  813. # After processing, ARGC is set to the number of elements left in ARGV[].
  814. # The option values are put in Options[].
  815. # On error, Err is set to a positive integer value so it can be checked for in
  816. # an END block.
  817. # Return value: The number of elements left in ARGV is returned.
  818. # Must keep OptErr global since it may be set by InitOpts().
  819. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  820. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  821.     if (MinArgs == "")
  822.     MinArgs = 0
  823.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  824.     optChars)
  825.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  826.     if (ArgsLeft >= 0) {
  827.         OptErr = "Not enough arguments"
  828.         Err = 4
  829.     }
  830.     else
  831.         Err = -ArgsLeft
  832.     printf "%s: %s.\nUse -h for help.\n%s\n",
  833.     Name,OptErr,Usage > "/dev/stderr"
  834.     exit 1
  835.     }
  836.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  837.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  838.     {
  839.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  840.     Err = -e
  841.     exit 1
  842.     }
  843.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  844.     {
  845.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  846.     Err = 1
  847.     exit 1
  848.     }
  849.     return ArgsLeft
  850. }
  851.  
  852. # ReadConfFile(): Read a file containing var/value assignments, in the form
  853. # <variable-name><assignment-char><value>.
  854. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  855. # line and whitespace between the variable name and the assignment character) 
  856. # is stripped.  Lines that do not contain an assignment operator or which
  857. # contain a null variable name are ignored, other than possibly being noted in
  858. # the return value.  If more than one assignment is made to a variable, the
  859. # first assignment is used.
  860. # Input variables:
  861. # File is the file to read.
  862. # Comment is the line-comment character.  If it is found as the first non-
  863. #     whitespace character on a line, the line is ignored.
  864. # Assign is the assignment string.  The first instance of Assign on a line
  865. #     separates the variable name from its value.
  866. # If StripWhite is true, whitespace around the value (whitespace between the
  867. #     assignment char and trailing whitespace on the line) is stripped.
  868. # VarPat is a pattern that variable names must match.  
  869. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  870. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  871. #     a line; no assignment operator is needed.  These variables are set in
  872. #     the output array with a null value.  Lines containing nothing but
  873. #     whitespace are still ignored.
  874. # Output variables:
  875. # Values[] contains the assignments, with the indexes being the variable names
  876. #     and the values being the assigned values.
  877. # Lines[] contains the line number that each variable occured on.  A flag set
  878. #     is record by giving it an index in Lines[] but not in Values[].
  879. # Return value:
  880. # If any errors occur, a string consisting of descriptions of the errors
  881. # separated by newlines is returned.  In no case will the string start with a
  882. # numeric value.  If no errors occur,  the number of lines read is returned.
  883. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  884. FlagsOK,
  885. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  886.     if (Comment != "")
  887.     Comment = "^" Comment
  888.     AssignLen = length(Assign)
  889.     if (VarPat == "")
  890.     VarPat = "."    # null varname not allowed
  891.     while ((Status = (getline Line < File)) == 1) {
  892.     LineNum++
  893.     sub("^[ \t]+","",Line)
  894.     if (Line == "")        # blank line
  895.         continue
  896.     if (Comment != "" && Line ~ Comment)
  897.         continue
  898.     if (Pos = index(Line,Assign)) {
  899.         Var = substr(Line,1,Pos-1)
  900.         Val = substr(Line,Pos+AssignLen)
  901.         if (StripWhite) {
  902.         sub("^[ \t]+","",Val)
  903.         sub("[ \t]+$","",Val)
  904.         }
  905.     }
  906.     else {
  907.         Var = Line    # If no value, var is entire line
  908.         Val = ""
  909.     }
  910.     if (!FlagsOK && Val == "") {
  911.         Errs = Errs \
  912.         sprintf("\nBad assignment on line %d of file %s: %s",
  913.         LineNum,File,Line)
  914.         continue
  915.     }
  916.     sub("[ \t]+$","",Var)
  917.     if (Var !~ VarPat) {
  918.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  919.         LineNum,File,Var)
  920.         continue
  921.     }
  922.     if (!(Var in Lines)) {
  923.         Lines[Var] = LineNum
  924.         if (Pos)
  925.         Values[Var] = Val
  926.     }
  927.     }
  928.     if (Status)
  929.     Errs = Errs "\nCould not read file " File
  930.     close(File)
  931.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  932. }
  933.  
  934. # Variables:
  935. # Data is stored in Options[].
  936. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  937. # Global vars:
  938. # Sets OptErr.  Uses ENVIRON[].
  939. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  940. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  941. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  942. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  943.     split("",filesRead,"")    # make awk know this is an array
  944.     NumVars = split(VarNames,Vars,",")
  945.     TypesInd = Ret = 0
  946.     if (EnvSearch == -1)
  947.     EnvSearch = NumVars
  948.     for (i = 1; i <= NumVars; i++) {
  949.     Var = Vars[i]
  950.     CharOpt = substr(OptList,++TypesInd,1)
  951.     if (CharOpt ~ "^[:;*()#<>&]$")
  952.         CharOpt = substr(OptList,++TypesInd,1)
  953.     Map[Var] = CharOpt
  954.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  955.     # Do not overwrite entries from environment
  956.     if (i <= EnvSearch && Var in ENVIRON &&
  957.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  958.         return Err
  959.     }
  960.  
  961.     numrcFiles = split(rcFiles,fNames,":")
  962.     for (i = 1; i <= numrcFiles; i++) {
  963.     rcFile = fNames[i]
  964.     if (rcFile ~ "^~/")
  965.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  966.     else if (rcFile ~ /^\$/) {
  967.         rcFile = substr(rcFile,2)
  968.         match(rcFile,"^[a-zA-Z0-9_]*")
  969.         envvar = substr(rcFile,1,RLENGTH)
  970.         if (envvar in ENVIRON)
  971.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  972.         else
  973.         continue
  974.     }
  975.     if (rcFile in filesRead)
  976.         continue
  977.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  978.     # may be the same
  979.     filesRead[rcFile]
  980.     if ("x" in Options)
  981.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  982.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  983.     if (retStr > 0)
  984.         READ_RCFILE = 1
  985.     else if (ret != "") {
  986.         OptErr = retStr
  987.         Ret = -1
  988.     }
  989.     for (Var in Lines)
  990.         if (Var in Map) {
  991.         if ((Err = AssignVal(Map[Var],
  992.         Var in Values ? Values[Var] : "",Options,Types[Var],
  993.         Var in Values,Var,0)) < 0)
  994.             return Err
  995.         }
  996.         else {
  997.         OptErr = sprintf(\
  998.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  999.         Lines[Var],rcFile)
  1000.         Ret = -1
  1001.         }
  1002.     }
  1003.  
  1004.     if ("x" in Options)
  1005.     for (Var in Map)
  1006.         if (Map[Var] in Options)
  1007.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1008.         "/dev/stderr"
  1009.         else
  1010.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1011.     return Ret
  1012. }
  1013.  
  1014. # OptSets is a semicolon-separated list of sets of option sets.
  1015. # Within a list of option sets, the option sets are separated by commas.  For
  1016. # each set of sets, if any option in one of the sets is in Options[] AND any
  1017. # option in one of the other sets is in Options[], an error string is returned.
  1018. # If no conflicts are found, nothing is returned.
  1019. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1020. # the exclusions presented by the first set of sets (ab,def,g) if:
  1021. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1022. # (a or b is in Options[]) AND (g is in Options) OR
  1023. # (d, e, or f is in Options[]) AND (g is in Options)
  1024. # An error will be returned due to the exclusions presented by the second set
  1025. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1026. # todo: make options given on command line unset options given in config file
  1027. # todo: that they conflict with.
  1028. function ExclusiveOptions(OptSets,Options,
  1029. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1030. SetNum,OSetNum) {
  1031.     NumSetSets = split(OptSets,SetSets,";")
  1032.     # For each set of sets...
  1033.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1034.     # NumSets is the number of sets in this set of sets.
  1035.     NumSets = split(SetSets[SetSet],Sets,",")
  1036.     # For each set in a set of sets except the last...
  1037.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1038.         s1 = Sets[SetNum]
  1039.         L1 = length(s1)
  1040.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1041.         # If any of the options in this set was given, check whether
  1042.         # any of the options in the other sets was given.  Only check
  1043.         # later sets since earlier sets will have already been checked
  1044.         # against this set.
  1045.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1046.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1047.             s2 = Sets[OSetNum]
  1048.             L2 = length(s2)
  1049.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1050.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1051.                 ErrStr = ErrStr "\n"\
  1052.                 sprintf("Cannot give both %s and %s options.",
  1053.                 c1,c2)
  1054.             }
  1055.     }
  1056.     }
  1057.     if (ErrStr != "")
  1058.     return substr(ErrStr,2)
  1059.     return ""
  1060. }
  1061.  
  1062. # The value of each instance of option Opt that occurs in Options[] is made an
  1063. # index of Set[].
  1064. # The return value is the number of instances of Opt in Options.
  1065. function Opt2Set(Options,Opt,Set,  count) {
  1066.     if (!(Opt in Options))
  1067.     return 0
  1068.     Set[Options[Opt]]
  1069.     count = Options[Opt,"count"]
  1070.     for (; count > 1; count--)
  1071.     Set[Options[Opt,count]]
  1072.     return count
  1073. }
  1074.  
  1075. # The value of each instance of option Opt that occurs in Options[] that
  1076. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1077. # Other values are made indexes of Set[].
  1078. # The return value is the number of instances of Opt in Options.
  1079. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1080.     ret = Opt2Set(Options,Opt,aSet)
  1081.     for (value in aSet)
  1082.     if (substr(value,1,1) == "!")
  1083.         nSet[substr(value,2)]
  1084.     else
  1085.         Set[value]
  1086.     return ret
  1087. }
  1088.  
  1089. # Returns true if option Opt was given on the command line.
  1090. function CmdLineOpt(Options,Opt,  i) {
  1091.     for (i = 1; (Opt,"num",i) in Options; i++)
  1092.     if (Options[Opt,"num",i] != 0)
  1093.         return 1
  1094.     return 0
  1095. }
  1096. ### End of ProcArgs library
  1097.